<?php
namespace App\Services;

use Ramsey\Uuid\Uuid;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Formatter;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Illuminate\Support\Facades\Log;
class WechatPayService {
    // 商户号
    private static $merchantId;
    // 「商户API证书」的「证书序列号」
    private static $merchantCertificateSerial;
    // 商户ApiV3密钥
    private static $merchantKey;
    // 应用id
    private static $appId;
    // 商户服务id
    private static $serviceId;

    /**
     * 初始化配置信息
     */
    public function __construct()
    {
        self::$merchantId = config('services.wechat.merchantId');

        self::$merchantCertificateSerial = config('services.wechat.merchantCertSerial');

        self::$merchantKey = config('services.wechat.merchantKey');

        self::$appId = config('services.wechat.appId');

        self::$serviceId = config('services.wechat.serviceId');
    }

    /**
     * @notes 公共请求方式
     * @param $url
     * @param $params
     * @param $methods
     * @return false|mixed
     * @author Evil丿
     * @date 2024/04/03 16:47
     */
    protected function client($url, $params, $methods = "POST")
    {
        // 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = 'file://'.base_path('cert/Wechat/apiclient_key.pem');
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        // 从本地文件中加载「微信支付平台证书」，用来验证微信支付应答的签名
        $platformCertificateFilePath = 'file://'.base_path('cert/Wechat/wechatpay.pem');
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);

        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid'      => self::$merchantId,
            'serial'     => self::$merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs'      => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ]
        ]);

        try {
            if($methods == "POST") {
                $resp = $instance->chain($url)->post(
                    [
                        'json'=> $params,
                        'Content-Type' => 'application/json'
                    ]
                );
            } else {
                $resp = $instance->chain($url)->get(
                    [
                        'query' => $params
                    ]
                );
            }

            if($resp -> getStatusCode() == 200) {
                return json_decode($resp -> getBody() ->getContents(), true);
            }

            return false;
        } catch (\Exception $e) {
            $message = $e->getMessage();
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                $message = $r->getBody() ->getContents();
            }
            Log::error(date('Ymd H:i:s', time()). ' : '.$message);
            throw new BadRequestHttpException($message);
        }
    }

    /**
     * @notes 生产签名
     * @param $params
     * @return string
     * @author Evil丿
     * @date 2024/04/03 16:47
     */
    public function makeSign($params)
    {
        uksort($params, function ($a, $b) {
            return strcoll($a, $b);
        });

        $params['key'] = self::$merchantKey;

        $sign_str = '';

        foreach($params as $k => $v) {
            $sign_str .= $k.'='.$v.'&';
        }

        $sign_str = trim($sign_str, '&');

        $hash_hmac =  hash_hmac('SHA256', $sign_str, self::$merchantKey);

        $strtoupper = strtoupper($hash_hmac);

        return $strtoupper;
    }

    /**
     * @notes ApiV3字符串解密
     * @param $associatedData
     * @param $nonceStr
     * @param $ciphertext
     * @return array
     * @author Evil丿
     * @date 2024/04/10 17:19
     */
    public function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $resource = AesGcm::decrypt($ciphertext, self::$merchantKey, $nonceStr, $associatedData);

        $array = (array) json_decode($resource, true);

        return $array;
    }

    /**
     * @notes 验签
     * @param $signature
     * @param $timestamp
     * @param $nonce
     * @param $body
     * @return array
     * @author Evil丿
     * @date 2024/06/07 10:44
     */
    public function verifySign($signature, $timestamp, $nonce, $body) {

        $platformCertificateFilePath = 'file://'.base_path('cert/Wechat/wechatpay.pem');

        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);

        $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$timestamp);
//        $timeOffsetStatus = 3000000 >= abs(Formatter::timestamp() - (int)$timestamp);

        $sign_str = Formatter::joinedByLineFeed($timestamp, $nonce, $body);

        $verifiedStatus = Rsa::verify(
            $sign_str,
            $signature,
            $platformPublicKeyInstance
        );

        if ($timeOffsetStatus && $verifiedStatus) {
            $inBodyArray = json_decode( $body, true);
            ['resource' => [
                'ciphertext'      => $ciphertext,
                'nonce'           => $nonceStr,
                'associated_data' => $associatedData
            ]] = $inBodyArray;
            return $this->decryptToString($associatedData, $nonceStr, $ciphertext);
        } else {
            return [];
        }
    }

    /**
     * @notes 创建支付分订单
     * @param $out_order_no
     * @param $attach
     * @return false|mixed
     * @author Evil丿
     * @date 2024/04/03 16:47
     */
    public function createOrder($out_order_no, $attach = [])
    {
        $params = [
            'service_id'                => self::$serviceId,
            'appid'                     => self::$appId,
            'out_order_no'              => $out_order_no,
            'service_introduction'      => '数文通-智慧零售',
            'time_range'    => [
                'start_time'            => 'OnAccept',
                'start_time_remark'     => '开门购物'
            ],
            'risk_fund'     => [
                'name'                  => 'CASH_DEPOSIT',
                'amount'                => 20000,
                'description'           => '单次购物上线金额'
            ],
            'notify_url'                => config('app.url').'/api/swt/notify/wechat/order',
            'need_user_confirm'         => true
        ];

        if(!empty($attach)) {
            $params['attach'] = urlencode(json_encode($attach, 256));
        }

        $url = "/v3/payscore/serviceorder";

        $res = $this->client($url, $params);

        return $res;
    }

    /**
     * @notes 取消支付分订单
     * @param $order_id
     * @param $reason
     * @return false|mixed
     * @author Evil丿
     * @date 2024/04/03 16:48
     */
    public function cancelOrder($out_order_no, $reason = "用户主动取消")
    {
        $params = [
            'appid'         => self::$appId,
            'service_id'    => self::$serviceId,
            'reason'        => $reason
        ];

        $url = "v3/payscore/serviceorder/".$out_order_no."/cancel";

        $res = $this->client($url, $params);

        return $res;
    }

    /**
     * @notes 查询支付分订单
     * @param $out_order_no
     * @return false|mixed
     * @author Evil丿
     * @date 2024/04/03 17:15
     */
    public function getOrder($out_order_no)
    {
        $params = [
            'out_order_no'  => $out_order_no,
            'appid'         => self::$appId,
            'service_id'    => self::$serviceId
        ];

        $url = "v3/payscore/serviceorder";

        $res = $this->client($url, $params, 'GET');

        return $res;
    }

    /**
     * @notes 获取微信支付分授权参数
     * @param $package
     * @param $order_id
     * @return array
     * @author Evil丿
     * @date 2024/05/06 10:51
     */
    public function getAuthParams($package, $order_id)
    {
        $data = [
            'mch_id'    => self::$merchantId,
            'package'   => $package,
            'timestamp' => time(),
            'nonce_str' => Uuid::uuid1($order_id),
            'sign_type' => 'HMAC-SHA256',
            'order_id'  => $order_id
        ];

        $data['sign'] = $this -> makeSign($data);

        return $data;
    }

    /**
     * @notes 发起支付分订单扣款
     * @param $out_order_no
     * @param $amount
     * @param $goodsList
     * @param $discountsList
     * @return false|mixed
     * @author Evil丿
     * @date 2024/06/07 10:44
     */
    public function payOrder($out_order_no, $amount, $goodsList, $discountsList = [])
    {
        $post_payments = [];

        foreach($goodsList as $goods) {
            if($goods['amount'] > 0) {
                $post_payments[] = [
                    'name'          => '商品信息',
                    'amount'        => (int) $goods['amount'],
                    'count'         => (int) $goods['quantity'],
                    'description'   => $goods['name']
                ];
            }
        }

        $post_discounts = [];

        foreach($discountsList as $discount) {
            if($discount['amount'] > 0) {
                $post_discounts[] = [
                    'name'          => $discount['name'],
                    'amount'        => (int) $discount['amount'],
                    'description'   => $discount['description']
                ];
            }
        }

        $now = time();

        $end_time = $now - 5;

        $params = [
            'appid'             => self::$appId,
            'service_id'        => self::$serviceId,
            'post_payments'     => $post_payments,
            'post_discounts'    => $post_discounts,
            'total_amount'      => (int) $amount,
            'time_range'        => [
                'end_time'          => date('YmdHis', $end_time),
                'end_time_remark'   => '关门结算'
            ]
        ];

        $url = "/v3/payscore/serviceorder/".$out_order_no."/complete";

        $res = $this->client($url, $params);

        return $res;
    }

    /**
     * @notes 支付分订单退款
     * @param $transaction_id
     * @param $out_refund_no
     * @param $refund
     * @param $total
     * @param $reason
     * @return false|mixed
     * @author Evil丿
     * @date 2024/08/05 17:00
     */
    public function refundOrder($transaction_id, $out_refund_no, $refund, $total, $reason = "")
    {
        $params = [
            'transaction_id'   => $transaction_id,
            'out_refund_no'    => $out_refund_no,
            'reason'           => $reason,
            'amount'           => [
                'refund'    => $refund,
                'total'     => $total,
                'currency'  => 'CNY',
            ]
        ];

        $url = "/v3/refund/domestic/refunds";

        $res = $this->client($url, $params);

        return $res;
    }

    public function payOrderByJsapi($order_sn, $total, $open_id)
    {
        $params = [
            'appid'             => self::$appId,
            'mchid'             => self::$merchantId,
            'description'       => '订单-'.$order_sn.'-二次支付',
            'out_trade_no'      => $order_sn,
            'notify_url'        => config('app.url').'/api/swt/notify/wechat/order/jsapi',
            'amount'            => [
                'total'     => $total,
                'currency'  => 'CNY',
            ],
            'payer'             => [
                'openid'    => $open_id,
            ]
        ];

        $url = "/v3/pay/transactions/jsapi";

        $res = $this->client($url, $params);

        $sign_params = [
            'appId'     => self::$appId,
            'timeStamp' => time(),
            'nonceStr'  => Uuid::uuid4()->toString(),
            'package'   => 'prepay_id=' . $res['prepay_id'],
        ];

        $merchantPrivateKeyFilePath = 'file://'.base_path('cert/Wechat/apiclient_key.pem');

        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);

        $sign_params += [
            'paySign'  => Rsa::sign(Formatter::joinedByLineFeed(...array_values($sign_params)), $merchantPrivateKeyInstance),
            'signType' => 'RSA'
        ];

        return $sign_params;
    }
}
